using System;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;

namespace HEFramework
{
    /// <summary>
    ///
    ///  UI滑动视图行为基类
    ///
    ///  * 子项必须为固定高度
    ///  * 仅支持一种类型的子项
    ///  * 仅虚拟化垂直滚动。仍然支持水平滚动，但不支持网格视图样式的布局
    ///
    /// ----------------------------------------
    /// 作者 hep
    /// 创建时间：2024-07-24 17:11:49
    /// ----------------------------------------
    /// </summary>
    [RequireComponent(typeof(ScrollRect))]
    public class UIScrollViewBehaviourBase : MonoBehaviour, IUIScrollViewBehaviour
    {
        [Tooltip("子节点物体")] public UIScrollViewItemBehaviourBase Item;
        [Tooltip("行间隔")] public float RowPadding = 15f;
        [Tooltip("事先预留的最小列表高度")] public float PreAllocHeight = 0;


        /// <summary>
        /// 滑动视图
        /// </summary>
        protected ScrollRect scrollRect;

        /// <summary>
        /// 复用的item数组
        /// </summary>
        protected IUIScrollViewItemBehaviour[] childItems;

        /// <summary>
        /// 循环列表中，第一个物体的索引，最开始每个第一个物体的索引都有一个原始索引，最顶部的物体的原始索引就是childBufferStart
        /// 由于列表是循环复用的，所以往下滑动时，childBufferStart会从0开始到n，然后又从0开始，以此往复
        /// 如果是往上滑动，则是从0到-n，再从0开始，以此往复
        /// </summary>
        protected int childBufferStart = 0;

        /// <summary>
        /// 列表中最顶部的物体的真实数据索引，比如有一百条数据，复用10个物体，当前最顶部是第60条数据，那么sourceDataRowStart就是59（注意索引从0开始）
        /// </summary>
        protected int sourceDataRowStart;

        /// <summary>
        /// 是否忽略滑动变化
        /// </summary>
        protected bool ignoreScrollChange = false;

        /// <summary>
        /// 上次更新时预留的视图高度
        /// </summary>
        protected float previousBuildHeight = 0;

        /// <summary>
        /// 缓存行个数
        /// </summary>
        protected const int rowsAboveBelow = 1;

        /// <summary>
        /// 列表行数
        /// </summary>
        protected int rowCount;


        public ScrollRect ScrollRect
        {
            get
            {
                if (scrollRect == null)
                {
                    scrollRect = GetComponent<ScrollRect>();
                }

                return scrollRect;
            }
        }

        /// <summary>
        /// 物体更新回调函数委托
        /// </summary>
        public Action<IUIScrollViewItemBehaviour, int> ItemCallback { get; set; }

        /// <summary>
        /// 列表行数，赋值时，会执行列表重新计算
        /// </summary>
        public int RowCount
        {
            get => rowCount;
            set
            {
                if (rowCount != value)
                {
                    rowCount = value;
                    // 先禁用滚动变化
                    ignoreScrollChange = true;
                    // 更新高度
                    UpdateContentHeight();
                    // 重新启用滚动变化
                    ignoreScrollChange = false;
                    // 重新计算item
                    ReorganiseContent(true);
                }
            }
        }

        /// <summary>
        /// 滑动条位置
        /// </summary>
        public float VerticalNormalizedPosition
        {
            get => scrollRect.verticalNormalizedPosition;
            set => scrollRect.verticalNormalizedPosition = value;
        }

        public virtual void Awake()
        {
            scrollRect = GetComponent<ScrollRect>();
            Item.GameObject.SetActive(false);
        }


        protected virtual void OnEnable()
        {
            scrollRect.onValueChanged.AddListener(OnScrollChanged);
            ignoreScrollChange = false;
        }

        protected virtual void OnDisable()
        {
            scrollRect.onValueChanged.RemoveListener(OnScrollChanged);
        }

        public Transform GetTransform()
        {
            return ScrollRect.transform;
        }

        public GameObject GetGameObject()
        {
            return ScrollRect.gameObject;
        }

        /// <summary>
        /// 供外部调用，强制滚动列表，使某一行显示在列表中
        /// </summary>
        /// <param name="_row">行号</param>
        /// <param name="_type">目标行显示在列表的位置：0顶部，1中心，2底部</param>
        public virtual void ScrollToRow(int _row, int _type)
        {
            scrollRect.verticalNormalizedPosition = GetRowScrollPosition(_row, _type);
        }

        /// <summary>
        /// 根据行号获取复用的物体对象
        /// </summary>
        /// <param name="_row">行号</param>
        public virtual IUIScrollViewItemBehaviour GetRowItem(int _row)
        {
            if (childItems != null &&
                _row >= sourceDataRowStart &&
                _row < sourceDataRowStart + childItems.Length &&
                _row < rowCount)
            {
                // 注意这里要根据行号计算复用的物体原始索引
                return childItems[WrapChildIndex(childBufferStart + _row - sourceDataRowStart)];
            }

            return null;
        }

        /// <summary>
        /// 供外部调用，强制刷新整个列表，比如数据变化了，刷新一下列表
        /// </summary>
        public virtual void Refresh()
        {
            ReorganiseContent(true);
        }

        /// <summary>
        /// 供外部调用，强制刷新整个列表的局部物体
        /// </summary>
        /// <param name="_rowStart">开始行</param>
        /// <param name="_count">数量</param>
        public virtual void Refresh(int _rowStart, int _count)
        {
            int sourceDataLimit = sourceDataRowStart + childItems.Length;
            for (int i = 0; i < _count; ++i)
            {
                int row = _rowStart + i;
                if (row < sourceDataRowStart || row >= sourceDataLimit)
                {
                    continue;
                }

                int bufferIndex = WrapChildIndex(childBufferStart + row - sourceDataRowStart);
                if (childItems[bufferIndex] != null)
                {
                    UpdateChild(childItems[bufferIndex], row);
                }
            }
        }

        /// <summary>
        /// 供外部调用，强制刷新整个列表的某一个物体
        /// </summary>
        public virtual void Refresh(IUIScrollViewItemBehaviour _item)
        {
            for (int i = 0; i < childItems.Length; ++i)
            {
                int index = WrapChildIndex(childBufferStart + i);
                if (childItems[index] != null && childItems[index] == _item)
                {
                    UpdateChild(childItems[i], sourceDataRowStart + i);
                    break;
                }
            }
        }

        /// <summary>
        /// 清空列表
        /// </summary>
        public virtual void Clear()
        {
            RowCount = 0;
        }

        /// <summary>
        /// 获得归一化的滚动位置，该位置将给定的行在视图中居中
        /// </summary>
        /// <param name="_row">行号</param>
        /// <returns></returns>
        protected float GetRowScrollPosition(int _row, int _type)
        {
            // 视图高
            float vpHeight = ViewportHeight();
            float rowHeight = RowHeight();
            // 将目标行滚动到列表目标位置时，列表顶部的位置
            float vpTop = 0;
            switch (_type)
            {
                case 0:
                {
                    vpTop = _row * rowHeight;
                }
                    break;
                case 1:
                {
                    // 目标行的中心位置与列表顶部的距离
                    float rowCentre = (_row + 0.5f) * rowHeight;
                    // 视口中心位置
                    float halfVpHeight = vpHeight * 0.5f;

                    vpTop = Mathf.Max(0, rowCentre - halfVpHeight);
                }
                    break;
                case 2:
                {
                    vpTop = (_row + 1) * rowHeight - vpHeight;
                }
                    break;
            }

            // 滚动后，列表底部的位置
            float vpBottom = vpTop + vpHeight;
            // 列表内容总高度
            float contentHeight = scrollRect.content.sizeDelta.y;
            // 如果滚动后，列表底部的位置已经超过了列表总高度，则调整列表顶部的位置
            if (vpBottom > contentHeight)
            {
                vpTop = Mathf.Max(0, vpTop - (vpBottom - contentHeight));
            }

            // 反插值，计算两个值之间的Lerp参数。也就是value在from和to之间的比例值
            return Mathf.InverseLerp(contentHeight - vpHeight, 0, vpTop);
        }

        /// <summary>
        /// 根据视图高度调整物体个数
        /// </summary>
        /// <returns></returns>
        protected virtual bool CheckChildItems()
        {
            // 列表视口高度
            float vpHeight = ViewportHeight();
            float buildHeight = Mathf.Max(vpHeight, PreAllocHeight);
            bool rebuild = childItems == null || buildHeight > previousBuildHeight;
            if (rebuild)
            {
                int childCount = Mathf.RoundToInt(0.5f + buildHeight / RowHeight());
                childCount += rowsAboveBelow * 2;

                if (childItems == null)
                {
                    childItems = new IUIScrollViewItemBehaviour[childCount];
                }
                else if (childCount > childItems.Length)
                {
                    Array.Resize(ref childItems, childCount);
                }

                // 创建物体
                for (int i = 0; i < childItems.Length; ++i)
                {
                    if (childItems[i] == null)
                    {
                        var item = Instantiate(Item.GameObject).GetComponent<IUIScrollViewItemBehaviour>();
                        item.Awake();
                        childItems[i] = item;
                    }

                    childItems[i].RectTransform.SetParent(scrollRect.content, false);
                    childItems[i].GameObject.SetActive(false);
                }

                previousBuildHeight = buildHeight;
            }

            return rebuild;
        }


        /// <summary>
        /// 列表滚动时，会回调此函数
        /// </summary>
        /// <param name="_normalisedPos">归一化的位置</param>
        protected virtual void OnScrollChanged(Vector2 _normalisedPos)
        {
            if (!ignoreScrollChange)
            {
                ReorganiseContent(false);
            }
        }

        /// <summary>
        /// 重新计算列表内容
        /// </summary>
        /// <param name="_clear">是否要清空列表重新计算</param>
        protected virtual void ReorganiseContent(bool _clear)
        {
            if (_clear)
            {
                scrollRect.StopMovement();
                scrollRect.verticalNormalizedPosition = 1;
            }

            bool childrenChanged = CheckChildItems();
            // 是否要更新整个列表
            bool populateAll = childrenChanged || _clear;

            float yMin = scrollRect.content.localPosition.y;

            // 第一个可见物体的索引
            int firstVisibleIndex = (int)(yMin / RowHeight());

            int newRowStart = firstVisibleIndex - rowsAboveBelow;

            // 滚动变化量
            int diff = newRowStart - sourceDataRowStart;
            if (populateAll || Mathf.Abs(diff) >= childItems.Length)
            {
                sourceDataRowStart = newRowStart;
                childBufferStart = 0;
                int rowIndex = newRowStart;
                foreach (var item in childItems)
                {
                    UpdateChild(item, rowIndex++);
                }
            }
            else if (diff != 0)
            {
                int newBufferStart = (childBufferStart + diff) % childItems.Length;

                if (diff < 0)
                {
                    // 向前滑动
                    for (int i = 1; i <= -diff; ++i)
                    {
                        // 得到复用物体的索引
                        int wrapIndex = WrapChildIndex(childBufferStart - i);
                        int rowIndex = sourceDataRowStart - i;
                        UpdateChild(childItems[wrapIndex], rowIndex);
                    }
                }
                else
                {
                    // 向后滑动
                    int preLastBufferIndex = childBufferStart + childItems.Length - 1;
                    int preLastRowIndex = sourceDataRowStart + childItems.Length - 1;
                    for (int i = 1; i <= diff; ++i)
                    {
                        int wrapIndex = WrapChildIndex(preLastBufferIndex + i);
                        int rowIndex = preLastRowIndex + i;
                        UpdateChild(childItems[wrapIndex], rowIndex);
                    }
                }

                sourceDataRowStart = newRowStart;
                childBufferStart = newBufferStart;
            }
        }

        private int WrapChildIndex(int _index)
        {
            while (_index < 0)
            {
                _index += childItems.Length;
            }

            return _index % childItems.Length;
        }

        /// <summary>
        /// 获取一行的高度，注意要加上RowPadding
        /// </summary>
        private float RowHeight()
        {
            return RowPadding + Item.RectTransform.rect.height;
        }

        /// <summary>
        /// 获取列表视口的高度
        /// </summary>
        private float ViewportHeight()
        {
            return scrollRect.viewport.rect.height;
        }

        /// <summary>
        /// 更新子物体
        /// </summary>
        /// <param name="_child"></param>
        /// <param name="_rowIndex"></param>
        protected virtual void UpdateChild(IUIScrollViewItemBehaviour _child, int _rowIndex)
        {
            if (_rowIndex < 0 || _rowIndex >= rowCount)
            {
                _child.GameObject.SetActive(false);
            }
            else
            {
                if (ItemCallback == null)
                {
                    Debug.Log("Missing Callback", this);
                    return;
                }

                // 移动到正确的位置
                var childRect = Item.RectTransform.rect;
                Vector2 pivot = Item.RectTransform.pivot;
                float y_top_pos = RowHeight() * _rowIndex;
                float y_pos = y_top_pos + (1f - pivot.y) * childRect.height;
                float x_pos = 0 + pivot.x * childRect.width;
                _child.RectTransform.anchoredPosition = new Vector2(x_pos, -y_pos);
                _child.Assign(this, _rowIndex);
                // 更新数据
                ItemCallback(_child, _rowIndex);
                _child.GameObject.SetActive(true);
            }
        }

        /// <summary>
        /// 更新content的高度
        /// </summary>
        protected virtual void UpdateContentHeight()
        {
            // 列表高度
            float height = Item.RectTransform.rect.height * rowCount + (rowCount - 1) * RowPadding;
            // 更新content的高度
            var sizeDelta = scrollRect.content.sizeDelta;
            scrollRect.content.sizeDelta = new Vector2(sizeDelta.x, height);
        }

        /// <summary>
        /// 释放所有子物体
        /// </summary>
        protected virtual void DisableAllChildren()
        {
            if (childItems != null)
            {
                for (int i = 0; i < childItems.Length; ++i)
                {
                    childItems[i].GameObject.SetActive(false);
                }
            }
        }
    }
}